這篇的完整的程式可以到 https://github.com/DanSnow/ironman-2020/tree/master/build-tool/packages/eslint-plugin
今天要來寫個 eslint plugin ,雖說是要寫個 plugin ,不過實際上是寫 eslint 的規則,這邊用我之前寫過的一個規則來示範,有時候會希望當 js 的物件是像這樣的時候 (比如是 vue 的 object 時):
export default {
name: 'Foo',
props: {},
data: () => ({}),
}
像上面這樣中間都有個空行,這邊用很簡單的方式來判斷是不是 vue 的 object ,就兩種:
export default
後Vue.extend
中eslint 的規則大致分成兩個部份 meta
跟 create
:
meta
: 這個規則的資料,如果這個規則可以被自動修復,也必須要定義在這裡create
: 建立規則的 AST visitor ,規則的檢查是在這邊做的跟之前寫 babel 的 plugin 很像,第一步是先打開 AST Explorer ,選 eslint 用的 parser espree
,來選要使用的節點,這邊選用的是 ObjectExpress
module.exports = {
meta: {
// 可以被修復的規則一定要定義,這邊除了 'whitespace' 外還有 'code' ,不過這只是分類上的問題
// 這邊因為是加換行,所以選 'whitespace'
fixable: 'whitespace',
// 可以定義可能會出現的訊息,這個就可以在統一的地方做管理,這是 eslint 推薦的作法
// 當然,你也可以直接把訊息寫在 context.report
messages: {
requireNewline: 'require newline between',
},
},
create: function (context) {
return {
ObjectExpression(node) {
if (
// 判斷父節點是 export default
node.parent.type === 'ExportDefaultDeclaration' ||
// 或父節點是 `Vue.extend`
(node.parent.type === 'CallExpression' && isVueExtend(node.parent.callee))
) {
// 取得 source code 物件,等下的 fixer 需要用到
const sourceCode = context.getSourceCode()
// 用 for 迴圈把物件的屬性兩兩抓一組,檢查中間有沒有加空行
for (let i = 0; i < node.properties.length - 1; ++i) {
// 這邊判斷的方法很簡單,前一個屬性結尾的行號,必須跟下一個屬性結尾的行號差 2 以上
// 也就是中間有 2 個以上的空行
if (node.properties[i + 1].loc.start.line - node.properties[i].loc.end.line < 2) {
context.report({
// 用預先定義的訊息
messageId: 'requireNewline',
// 或是你可以直接把訊息寫進來
// message: 'require newline between',
// 指定出錯的位置,因為是在兩個屬性的中間,所以就用前一個的 end 與後一個的 start 來指定
loc: {
start: node.properties[i].loc.end,
end: node.properties[i + 1].loc.start,
},
// 如果出錯的位置正好是某個 AST 的節點,那也可以傳入節點
// node: node
fix(fixer) {
// 這邊是自動修復的部份,晚點再來講
},
})
}
}
}
},
}
},
}
正常來說 eslint 的 plugin 需要照著 eslint 的套件命名規則才有辦法載入,不過這邊為了方便測試,就直接呼叫 eslint 的函式把自訂的規則加入:
// eslint 的 Linter
const { Linter } = require('eslint')
// 我們的規則
const rule = require('./space-between-properties')
const linter = new Linter()
// 幫規則設定一個 id
const id = 'space-between-properties'
// 加入規則的定義,也就是上面的那個東西
linter.defineRule(id, rule)
// 執行規則
const res = linter.verify(
`
export default {
name: 'Foo',
props: {},
}
`,
{
rules: {
[id]: 'error',
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2015,
},
}
)
// 如果有錯誤的話就印出來
if (res.length) {
console.log(res)
}
沒意外的話應該會看到上面有印出東西來,再來是加上自動修復的部份:
// 接續上面的 fix 的部份
fix(fixer) {
// 取得兩個節點中間的 token
const tokens = sourceCode.getTokensBetween(node.properties[i], node.properties[i + 1])
// 「通常」中間只會有逗號,所以唯一的節點就是逗號
const comma = tokens[0]
// 要求 eslint 在逗號後加上換行
return fixer.insertTextAfterRange(comma.range, '\n')
}
這邊修復也很簡單,就是在逗號後加上換行而已,不過上面也特別說了「通常」,其實這個 plugin 你只要在 ,
後加上註解就會出問題了
eslint 會在最後一次把修復加上去,然後再跑一次所有規則,如果還是有可以修復的問題就再跑一次,直到沒有可以自動修復的問題,所以也不用擔心會弄壞其它的 plugin 提供的規則,不過如果有規則互相衝突的話不知道會怎麼樣,有興趣的人可以自己試試
這是這系列主要的技術文章的最後一篇了